﻿using System;
using System.Collections.Generic;
using System.Data;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Threading.Tasks;
using System.Windows.Forms;
using Autofac;
using log4net;
using PortableVersion.Core;
using PortableVersion.Core.FileSystem;
using PortableVersion.Core.FileSystem.Services;
using PortableVersion.Core.General;
using IContainer = Autofac.IContainer;

namespace PortableVersion
{
    public partial class CheckInForm : Form
    {
        private readonly ILog _log;
        private readonly IConfigurationProvider _config;
        private readonly IContainer _container = BootStapper.Container.Instance;
        private List<Diff<FileItem>> _conflicts;
        private ConflictState _conflictState;
        private List<Diff<FileItem>> _sfeDiffs;
        private PendingChanges _pendingChagesForm;
        private List<DiffApplicationResult> _errors;

        public CheckInForm(ILog log, IConfigurationProvider config)
        {
            _log = log;
            _config = config;
            InitializeComponent();
        }

        private void button1_Click(object sender, EventArgs e)
        {
            if (openFile.ShowDialog() == DialogResult.OK)
            {
                CheckInTxt.Text = openFile.FileName;
            }
        }

        private void button2_Click(object sender, EventArgs e)
        {
            if (String.IsNullOrEmpty(CheckInTxt.Text))
            {
                MessageBox.Show(@"Please provide valid input.");
                return;
            }
            progressBar.Visible = true;
            var checkInSvc = _container.Resolve<CheckInService>(
                        new[]
                                {
                                    new NamedParameter("sourcePath", Path.GetDirectoryName(CheckInTxt.Text)),
                                    new NamedParameter("tfsConfigPath", LocalizePath(_config.GetValue<string>("TfsConfig")))
                                });
            checkInSvc.DiffsRecalculated += CheckInSvc_DiffsRecalculated;
            checkInSvc.CheckInFinished += checkInSvc_CheckInFinished;
            timer1.Start();
            Task.Factory.StartNew(
                checkInSvc.Run
                ,TaskCreationOptions.LongRunning
                )
                .ContinueWith(
                    t =>
                    {
                        progressBar.Visible = false;
                        timer1.Stop();
                        if (t.IsFaulted && t.Exception != null)
                        {
                            _log.Error("Check in did not succeed.", t.Exception.InnerException);
                            MessageBox.Show(t.Exception.InnerException.Message);
                            if (_pendingChagesForm != null)
                                _pendingChagesForm.ReadOnly = false;
                        }
                        else
                        {
                            if (_pendingChagesForm != null)
                                _pendingChagesForm.ReadOnly = true;
                        }
                    }
                        , TaskScheduler.FromCurrentSynchronizationContext()
                );
        }

        private void checkInSvc_CheckInFinished(object sender, DiffApplicationEventArgs e)
        {
            var numSuc = e.Diffs.Count(d => d.Successful);
            var numFailed = e.Diffs.Count() - numSuc;
            _errors = e.Diffs.Where(d => !d.Successful).ToList();
            if (_errors.Any())
            {
                ResetErrors();
            }
            MessageBox.Show(String.Format("Check In finished with {0} sucessfull adn {1} failed operations. \r\nYou should manually fix the failed check-ins.", 
                numSuc, numFailed));
            
        }

        private static object LocalizePath(string subPath)
        {
            return Path.Combine(Path.GetDirectoryName(Application.ExecutablePath), subPath);
        }

        private void CheckInSvc_DiffsRecalculated(object sender, DiffEventArgs e)
        {
            _conflicts = e.Conflicts.ToList();
            _conflictState = e.ConflictState;
            _sfeDiffs = e.SafeDiffs.ToList();
            _log.Info(@"New diff data received at ChecInForm");
            Invoke(new Action(() =>
                                  {
                                      ShowPendingChanges(true);
                                      ResetDiffs();
                                  }));
        }

        private void ResetErrors()
        {
            var errTbl = new DataTable("Errors");
            errTbl.Columns.AddRange(new[]
                                         {
                                             new DataColumn("File", typeof (string)),
                                             new DataColumn("Diff Type", typeof (string)),
                                             new DataColumn("Slave Event", typeof (string)),
                                             new DataColumn("Master Event", typeof (string)),
                                             new DataColumn("Desc", typeof (string)),
                                         });
            _errors.ForEach(c =>
                            errTbl.Rows.Add(
                                c.Diff.Source.NullSafe(s => s.Path) ?? c.Diff.Meta.Path, //If c is deleted -> No source
                                Enum.GetName(typeof(DiffType), c.Diff.DiffType),
                                Enum.GetName(typeof(DiffEvent), c.Diff.SourceEvent),
                                Enum.GetName(typeof (DiffEvent), c.Diff.TargetEvent),
                                FormatErrorDescription(c)
                                ));
            if (_pendingChagesForm != null)
            {
                _pendingChagesForm.SetErrors(errTbl);
            }
        }

        private static string FormatErrorDescription(DiffApplicationResult diffApplicationResult)
        {
            return String.Format("Applying changes of '{0}' failed with the following exception: \r\n{1} ",
                                 diffApplicationResult.Diff.Source.NullSafe(s => s.Path) ??
                                 diffApplicationResult.Diff.Meta.Path,
                                 diffApplicationResult.Exception.Message);
        }

        private void ResetDiffs()
        {
            //Create datatables from diffs
            var safeTbl = new DataTable("Safe Changes");
            safeTbl.Columns.AddRange(new []
                                         {
                                             new DataColumn("File", typeof(string)), 
                                             new DataColumn("ChangeType", typeof(string)), 
                                             new DataColumn("Desc", typeof(string)), 
                                         });
            _sfeDiffs.ForEach(d => safeTbl.Rows.Add(
                d.Source.NullSafe(s => s.Path) ?? d.Meta.NullSafe(m => m.Path) //If d is deleted there would be no soruce 
                , Enum.GetName(typeof(DiffEvent), d.SourceEvent),
                String.Format(" Slave file (dir) '{0}' is {1}. \n Master file is {2}. \n Hence, the checkin will be {3}.",
                d.Source.NullSafe(s => s.Path) ?? d.Meta.NullSafe(m => m.Path)
                , d.SourceEvent, d.TargetEvent, d.DiffType)
                ));

            var confTbl = new DataTable("Conflicts");
            confTbl.Columns.AddRange(new[]
                                         {
                                             new DataColumn("File", typeof (string)),
                                             new DataColumn("Conflict Type", typeof (string)),
                                             new DataColumn("Slave Event", typeof (string)),
                                             new DataColumn("Master Event", typeof (string)),
                                             new DataColumn("Resolution", typeof (string)),
                                             new DataColumn("MetaKey", typeof (string)),
                                             new DataColumn("Desc", typeof (string)),
                                         });
            _conflicts.ForEach(c =>
                                   {
                                       var action = _conflictState.Items.FirstOrDefault(
                                           i => i.MetaKey == c.Meta.Key
                                           );
                                       confTbl.Rows.Add(
                                                        c.Source.NullSafe(s => s.Path) ?? c.Meta.Path, //If c is deleted -> No source
                                                        Enum.GetName(typeof (DiffType), c.DiffType),
                                                        Enum.GetName(typeof (DiffEvent), c.SourceEvent),
                                                        Enum.GetName(typeof (DiffEvent), c.TargetEvent),
                                                        action != null
                                                            ? Enum.GetName(typeof (ConflictAction), action.ActionTaken)
                                                            : "",
                                                        c.Meta.Key,
                                                        FormatConflictDescription(c, action)
                                           );
                                   });
            if (_pendingChagesForm != null)
            {
                _pendingChagesForm.SetSafeChanges(safeTbl);
                _pendingChagesForm.SetConflicts(confTbl);
            }
        }

        private string FormatConflictDescription(Diff<FileItem> diff, ConflictItem action)
        {
            switch (diff.DiffType)
            {
                case DiffType.ContentConflict :
                    return
                        String.Format(
                            @"The slave file '{0}' has content conflict with the master file '{1}'. You can resolve the conflict or ignore it.",
                            diff.Source.Path, diff.Target.Path);
                case DiffType.LocationConflict :
                    return
                        String.Format(
                            "Slave file '{0}' is '{1}', and the master file '{2}' is {3}. \r\nWe don't know how to resolve this conflict.",
                            diff.Meta.Path, diff.SourceEvent, 
                            diff.Target.NullSafe( t => t.Path) ?? diff.Meta.Path, diff.TargetEvent);
                default:
                    return "Invalid conflict!";

            }
        }

        private void ShowPendingChanges(bool readOnly)
        {
            if (_pendingChagesForm != null && !_pendingChagesForm.IsDisposed)
            {
                _pendingChagesForm.Show();
                return;
            }
            _pendingChagesForm = _container.Resolve<PendingChanges>();
            _pendingChagesForm.ReadOnly = true;
            _pendingChagesForm.Show();
            _pendingChagesForm.OpenMergeTool = metaKey => OpenMergeTool(metaKey);
            _pendingChagesForm.UpdateBack = tbl => UpdateConflictFromTable(tbl);
        }

        private void UpdateConflictFromTable(DataTable tbl)
        {
            var newActions = new List<ConflictItem>();
            foreach (DataRow row in tbl.Rows)
            {
                    var key = (string)row["MetaKey"];
                    var resolution = (string) row["Resolution"];
                    if (String.IsNullOrEmpty(resolution))
                        continue;
                    var rowAction = (ConflictAction)Enum.Parse(typeof (ConflictAction), resolution);
                    var con = _conflicts.First(c => c.Meta.Key == key);
                    newActions.Add(new ConflictItem
                                       {
                                               ActionTaken = rowAction,
                                               MetaKey = key,
                                               MonitoredTime = DateTime.UtcNow,
                                               SourcePath = con.Source.NullSafe(s => s.Path),
                                               TargetPath = con.Target.NullSafe(t => t.Path)
                                           });
            }
            var confResolver = _container.Resolve<IConflictResolutionService<FileItem>>(new[] { new NamedParameter("tfsConfigPath", LocalizePath("Tfs.config")) });
            //Just save the actions to the list of conflict actions on disk
            var confFile = Path.Combine(Path.GetDirectoryName(CheckInTxt.Text)
                                        , _config.GetValue<string>(ConfigurationProvider.Keys.ConflictFileName));
            confResolver.LoadState(confFile);
            confResolver.ApplyActions(newActions);
            confResolver.SaveState(confFile);
            _conflictState = confResolver.ConflictState;
        }

        private void OpenMergeTool(string metaKey)
        {
            var mergeCommand = _config.GetValue<string>(ConfigurationProvider.Keys.MergeToolCommand);
            var mergeCommandParams = _config.GetValue<string>(ConfigurationProvider.Keys.MergeToolCommandParams);
            if (String.IsNullOrEmpty(mergeCommand))
            {
                MessageBox.Show(@"No merge tool is defined in the config file");
                return;
            }
            var diff = _conflicts.First(c => c.Meta.Key == metaKey);
            var act = _conflictState.Items.First(st => st.MetaKey == metaKey);
            var slaveFile = diff.Source.Path;
            var masterFile = diff.Target.Path;
            var mergeFile = masterFile + "." + act.Id;
            if (!File.Exists(mergeFile))
                File.Copy(slaveFile, mergeFile, false);
            mergeCommandParams = mergeCommandParams.Replace(@"%1", slaveFile)
                .Replace(@"%2", masterFile)
                .Replace(@"%3", mergeFile);
            Task.Factory.StartNew(() =>
                                      {
                                          var p = Process.Start(mergeCommand, mergeCommandParams);
                                          p.WaitForExit();
                                          if (p.ExitCode != 0)
                                          {
                                              BeginInvoke(
                                                  new Action(
                                                      () =>
                                                      MessageBox.Show(
                                                          String.Format("Could not execute the process '{0}'",
                                                                        mergeCommand))));
                                          }
                                      }, TaskCreationOptions.LongRunning);
        }

        private void timer1_Tick(object sender, EventArgs e)
        {
            if (progressBar.Value > 95)
            {
                progressBar.Value = 0;
            }
            progressBar.Value += 5;
        }

        private void CheckInForm_Load(object sender, EventArgs e)
        {
        }

        internal void SetCheckIn(string file)
        {
            CheckInTxt.Text = file;
        }
    }
}
